import numpy as np
from sko.GA import GA_TSP
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Patch
import datetime
import time


# 生产车间调度安排类
class PMPAssignment:

    def __init__(self, _line_list, _product_list, _product_cost_matrix, _transport_cost_matrix,
                 _product_assign_sequence):
        # 产线信息
        self.line_list = _line_list
        # 生产任务信息
        self.product_list = _product_list
        # 工时信息
        self.product_cost_matrix = _product_cost_matrix
        # 转产开销信息
        self.transport_cost_matrix = _transport_cost_matrix
        # 工作安排顺序信息
        self.product_assign_sequence = _product_assign_sequence
        # 产线实体类
        lineEntityList = []
        for line_id, line_info in enumerate(self.line_list):
            lineEntity = LineEntity(line_info, self.product_list, self.product_cost_matrix,
                                    self.transport_cost_matrix)
            lineEntityList.append(lineEntity)
        self.line_entity_list = lineEntityList

    # 安排一个工作
    def eachProductAssign(self, each_product_assign_id):
        # 遍历整个人员列表，获取其开工信息，确认哪个时间段是当前任务能安排的，以最短时长策略安排
        # 即开工时间加上本任务的工作时间在时间轴上最短
        min_time = 9999999
        select_line = None
        for index, value in enumerate(self.line_entity_list):
            line_id = value.line_info['line_id']
            # 获取该人员空闲时间,如果没任务则为0
            line_info_free_time = value.now_product_end_time
            # 获取该产线上一个任务的id,如果是没任务则为-1
            line_info_prev_product_id = value.now_product_id
            # 获取上一个任务到目前任务地址所需要的时间开销,
            # 如果没任务line_info_prev_product_id会是-1,转移开销为起点准备时间
            if line_info_prev_product_id == -1:
                this_transport_cost = self.transport_cost_matrix[-1][each_product_assign_id]
            else:
                this_transport_cost = self.transport_cost_matrix[line_info_prev_product_id][each_product_assign_id]
            # 获取当前人员需要做该任务的时间开销
            this_product_cost = self.product_cost_matrix[line_id][each_product_assign_id]
            # 如果line_info_free_time + this_transport_cost + this_product_cost小于min_time
            # 则更新选择的select_person_id和min_time,求出最应该安排的人
            if (line_info_free_time + this_transport_cost + this_product_cost) <= min_time:
                min_time = (line_info_free_time + this_transport_cost + this_product_cost)
                select_line = value
        select_line.updateLineStatus(each_product_assign_id)

    # 安排一个序列的工作
    def productAssign(self):
        for index, value in enumerate(self.product_assign_sequence):
            self.eachProductAssign(value)

    # 画甘特图
    def drawGantt(self):
        MColor = ['red', 'blue', 'yellow', 'orange', 'green', 'moccasin', 'purple',
                  'pink', 'navajowhite', 'Thistle', 'Magenta', 'SlateBlue', 'RoyalBlue',
                  'Aqua', 'floralwhite', 'ghostwhite', 'goldenrod', 'mediumslateblue',
                  'navajowhite', 'navy', 'sandybrown']
        fig = plt.figure(figsize=(15, 6), dpi=70)
        # 横众轴显示字体为‘仿宋’的中文标签
        plt.rcParams['font.sans-serif'] = ['FangSong']
        plt.rcParams['axes.unicode_minus'] = False
        for each_line in self.line_entity_list:
            line_id = each_line.line_info['line_id']
            line_name = each_line.line_info['line_name']
            product_done_list = each_line.product_done_list
            for each_product in product_done_list:
                # 画交通时间开销
                plt.barh(line_name, width=each_product['transport_cost_time'],
                         height=0.8,
                         color='white',
                         edgecolor='black',
                         left=each_product['prev_product_end_time'])
                # 画工作开销
                plt.barh(line_name, width=each_product['product_cost_time'],
                         height=0.8,
                         color=MColor[each_product['product_class_id']],
                         edgecolor='black',
                         left=each_product['start_time'])
                # 交通开销表标明文字
                plt.text(x=each_product['prev_product_end_time'],
                         y=line_id - 0.1,
                         color='black',
                         weight='bold',
                         s=each_product['prev_product_name'] + '\n' + each_product['product_name'],
                         size=8, fontproperties='SimSun')
                # 工作开销标明文字
                plt.text(x=each_product['start_time'],
                         y=line_id - 0.1,
                         color='black',
                         weight='bold',
                         s=each_product['product_name'],
                         size=12, fontproperties='SimSun')

        plt.ylabel("产线", size=20, fontproperties='SimSun')
        plt.xlabel("小时", size=20, fontproperties='SimSun')
        plt.tick_params(labelsize=20)
        plt.tick_params(direction='in')
        plt.show()

    # 获取适应度
    def getFitness(self):
        maxFitNess = -1
        for each_line in self.line_entity_list:
            if each_line.now_product_end_time >= maxFitNess:
                maxFitNess = each_line.now_product_end_time
        return maxFitNess


# 生产车间调度产线
class LineEntity:

    def __init__(self, _line_info, _product_list, _product_cost_matrix, _transport_cost_matrix):
        self.line_info = _line_info
        self.product_list = _product_list
        self.product_cost_matrix = _product_cost_matrix
        self.transport_cost_matrix = _transport_cost_matrix
        self.now_product_start_time = 0
        self.now_product_end_time = 0
        self.now_product_prev_trans_time = 0
        self.now_product_id = -1
        self.product_done_list = []

    # 更新产线的工作列表，包括任务号的阶段，起始时间
    def updateLineStatus(self, product_id):
        _temp_product_done = {}
        _temp_product_done['product_id'] = product_id
        _temp_product_done['line_id'] = self.line_info['line_id']
        _temp_product_done['product_name'] = self.product_list[product_id]['product_name']
        _temp_product_done['product_class_id'] = self.product_list[product_id]['product_class_id']
        _temp_product_done['product_class_name'] = self.product_list[product_id]['product_class_name']
        # 先查出前置任务的id和时间，如果是-1则表示没有前置任务
        prev_product_id = -1
        prev_product_end_time = 0
        prev_product_name = -1
        # 如果prev_product_id是-1则表示刚开始，如果不是则从product_done_list列表中获取倒数第一个任务作为本次任务的前置任务
        if len(self.product_done_list) == 0:
            prev_product_id = -1
            prev_product_end_time = 0
            prev_product_name = '开始准备'
        else:
            prev_product_id = self.product_done_list[-1]['product_id']
            prev_product_end_time = self.product_done_list[-1]['end_time']
            prev_product_name = self.product_done_list[-1]['product_name']
        _temp_product_done['prev_product_id'] = prev_product_id
        _temp_product_done['prev_product_end_time'] = prev_product_end_time
        _temp_product_done['prev_product_name'] = prev_product_name
        # 计算前置任务到现任务的转产时间开销，
        # 如果没有前置任务则转产时间起点准备时间
        if prev_product_id == -1:
            transport_cost_time = self.transport_cost_matrix[-1][product_id]
            _temp_product_done['transport_cost_time'] = transport_cost_time
        else:
            # 如果有前置任务则交通时间为从起点开始的_transport_cost_matrix的时间
            transport_cost_time = self.transport_cost_matrix[prev_product_id][product_id]
            _temp_product_done['transport_cost_time'] = transport_cost_time
        # 计算当前任务的开始时间:前置任务结束时间加上转产时间
        if prev_product_id == -1:
            start_time = transport_cost_time
        else:
            start_time = prev_product_end_time + transport_cost_time
        _temp_product_done['start_time'] = start_time
        # 工时信息表中查出任务的时间开销
        product_cost_time = self.product_cost_matrix[self.line_info['line_id']][product_id]
        _temp_product_done['product_cost_time'] = product_cost_time
        _temp_product_done['end_time'] = start_time + product_cost_time
        # 将工作记录移入列表
        self.product_done_list.append(_temp_product_done)
        # 将当前开工时间，当前任务前置交通时间和当前任务结束时间更新，便于查找
        self.now_product_start_time = _temp_product_done['start_time']
        self.now_product_end_time = _temp_product_done['end_time']
        self.now_product_prev_trans_time = _temp_product_done['transport_cost_time']
        self.now_product_id = _temp_product_done['product_id']


# 基本测试
def base_test():
    # 产线信息
    line_list = [
        {'line_id': 0, 'line_name': '第一产线'},
        {'line_id': 1, 'line_name': '第二产线'},
        {'line_id': 2, 'line_name': '第三产线'},
    ]
    # 生产任务信息
    product_list = [
        {'product_id': 0, 'product_name': '生产车型1'},
        {'product_id': 1, 'product_name': '生产车型2'},
        {'product_id': 2, 'product_name': '生产车型3'},
        {'product_id': 3, 'product_name': '生产车型4'},
        {'product_id': 4, 'product_name': '生产车型5'},
        {'product_id': 5, 'product_name': '生产车型6'},
        {'product_id': 6, 'product_name': '生产车型7'},
        {'product_id': 7, 'product_name': '生产车型8'},
        {'product_id': 8, 'product_name': '生产车型9'},
        {'product_id': 9, 'product_name': '生产车型10'},
        {'product_id': 10, 'product_name': '生产车型11'},
        {'product_id': 11, 'product_name': '生产车型12'},
    ]
    # 工时信息，用二维数组表示,表示每个产线完成任务所需要的时间，行数为产线号，列为生产任务号，
    # 比如第一行，第四列表示1号产线（line_id为0）完成4号任务（product_id为3）的时间
    product_cost_matrix = [
        [4, 2, 3, 4, 2, 4, 2, 3, 4, 2, 4, 2],
        [4, 4, 2, 2, 2, 4, 4, 2, 2, 2, 4, 4],
        [4, 3, 1, 1, 2, 4, 3, 1, 1, 2, 4, 3],
    ]
    # 固定生成13*13的转产开销矩阵，默认第一行为第一个任务到各个任务的转产时间开销，
    # 多余的最后一行一列表示从开始到生产该机型的时间
    # 为便于调试，指定种子值使得该矩阵永远是这个样子
    np.random.seed(1234)
    transport_cost_matrix = np.random.randint(1, 10, (len(product_list) + 1, len(product_list) + 1))
    np.random.seed(None)
    # 工作的安排顺序
    product_assign_sequence = [1, 0, 2, 3, 5, 6, 4, 10, 9, 8, 11, 7]
    pmpAssignment = PMPAssignment(line_list, product_list, product_cost_matrix, transport_cost_matrix,
                                  product_assign_sequence)
    pmpAssignment.productAssign()
    pmpAssignment.drawGantt()


# 负责测试
def complex_test():
    # 产线信息
    line_list = [
        {'line_id': 0, 'line_name': '第一产线'},
        {'line_id': 1, 'line_name': '第二产线'},
        {'line_id': 2, 'line_name': '第三产线'},
    ]
    # 生产任务信息
    product_list = [
        {'product_id': 0, 'product_name': '生产订单1', 'product_class_id': 0, 'product_class_name': '车型1'},
        {'product_id': 1, 'product_name': '生产订单2', 'product_class_id': 0, 'product_class_name': '车型1'},
        {'product_id': 2, 'product_name': '生产订单3', 'product_class_id': 0, 'product_class_name': '车型1'},
        {'product_id': 3, 'product_name': '生产订单4', 'product_class_id': 0, 'product_class_name': '车型1'},
        {'product_id': 4, 'product_name': '生产订单5', 'product_class_id': 1, 'product_class_name': '车型2'},
        {'product_id': 5, 'product_name': '生产订单6', 'product_class_id': 1, 'product_class_name': '车型2'},
        {'product_id': 6, 'product_name': '生产订单7', 'product_class_id': 1, 'product_class_name': '车型2'},
        {'product_id': 7, 'product_name': '生产订单8', 'product_class_id': 1, 'product_class_name': '车型2'},
        {'product_id': 8, 'product_name': '生产订单9', 'product_class_id': 2, 'product_class_name': '车型3'},
        {'product_id': 9, 'product_name': '生产订单10', 'product_class_id': 2, 'product_class_name': '车型3'},
        {'product_id': 10, 'product_name': '生产订单11', 'product_class_id': 2, 'product_class_name': '车型3'},
        {'product_id': 11, 'product_name': '生产订单12', 'product_class_id': 2, 'product_class_name': '车型3'},
    ]
    # 工时信息，用二维数组表示,表示每个产线完成任务所需要的时间，行数为产线号，列为生产任务号，
    # 比如第一行，第四列表示1号产线（line_id为0）完成4号任务（product_id为3）的时间
    product_cost_matrix = [
        [3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5],
        [3, 3, 3, 3, 5, 5, 5, 5, 5, 5, 5, 5],
        [2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4],
    ]
    # 固定生成13*13的转产开销矩阵，默认第一行为第一个任务到各个任务的转产时间开销，
    # 多余的最后一行一列表示从开始到生产该机型的时间，目前全部是1
    transport_cost_matrix = [
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    ]
    # 工作的安排顺序
    product_assign_sequence = [1, 0, 2, 3, 5, 6, 4, 10, 9, 8, 11, 7]
    pmpAssignment = PMPAssignment(line_list, product_list, product_cost_matrix, transport_cost_matrix,
                                  product_assign_sequence)
    pmpAssignment.productAssign()
    pmpAssignment.drawGantt()
    print (pmpAssignment.getFitness())

if __name__ == '__main__':
    complex_test()
    pass
